// 版权归Go作者所有。版权所有。
// 此源代码的使用受BSD样式
// 许可证的约束，该许可证可以在许可证文件中找到。

package poll

import (
	"internal/syscall/unix"
	"runtime"
	"sync"
	"sync/atomic"
	"syscall"
	"unsafe"
)

const (
	// 接头非阻塞调用接头（2）非阻塞。
	spliceNonblock = 0x2

	// maxSpliceSize是Splice请求内核在单个调用中移动到Splice（2）的最大数据量。
	maxSpliceSize = 4 << 20
)

// Splice使用
// Splice系统调用将src到dst的数据传输最多剩余字节，以最小化从用户空间到用户空间的数据副本。
// 
// Splice从池中获取管道缓冲区，或者根据需要创建一个新的管道缓冲区，作为数据传输的缓冲区。
// src和dst都必须是面向流的套接字。
// 
// 如果出错！=nil，sc是导致错误的系统调用。
func Splice(dst, src *FD, remain int64) (written int64, handled bool, sc string, err error) {
	p, sc, err := getPipe()
	if err != nil {
		return 0, false, sc, err
	}
	defer putPipe(p)
	var inPipe, n int
	for err == nil && remain > 0 {
		max := maxSpliceSize
		if int64(max) > remain {
			max = int(remain)
		}
		inPipe, err = spliceDrain(p.wfd, src, max)
		// 如果splice未返回
		// 错误或EINVAL以外的错误，则认为该操作已处理。EINVAL表示
		// 内核不支持src套接字类型的拼接。
		// 失败的系统调用不会消耗任何数据，因此返回到通用副本是安全的。
		// 
		// STIPLEDRAIN永远不应该返回EAGAIN，所以如果出现错误！=无，
		// 拼接无法继续。
		// 
		// 如果inPipe==0&&err==nil，则src处于EOF，并且
		// 传输完成。
		handled = handled || (err != syscall.EINVAL)
		if err != nil || inPipe == 0 {
			break
		}
		p.data += inPipe

		n, err = splicePump(dst, p.rfd, inPipe)
		if n > 0 {
			written += int64(n)
			remain -= int64(n)
			p.data -= n
		}
	}
	if err != nil {
		return written, handled, "splice", err
	}
	return written, true, "", nil
}

// 将数据从套接字移动到管道。
// 
// 不变量：进入排水管时，管道为空。它要么处于
// 初始状态，要么之前泵已将其清空。
// 
// 鉴于此，SpiceDrain可以合理地假设管道已准备好进行
// 写入，因此如果splice返回EAGAIN，一定是因为套接字未准备好读取。
// 
// 如果拼接漏极返回（0，零），src处于EOF。
func spliceDrain(pipefd int, sock *FD, max int) (int, error) {
	if err := sock.readLock(); err != nil {
		return 0, err
	}
	defer sock.readUnlock()
	if err := sock.pd.prepareRead(sock.isFile); err != nil {
		return 0, err
	}
	for {
		n, err := splice(pipefd, sock.Sysfd, max, spliceNonblock)
		if err == syscall.EINTR {
			continue
		}
		if err != syscall.EAGAIN {
			return n, err
		}
		if err := sock.pd.waitRead(sock.isFile); err != nil {
			return n, err
		}
	}
}

// 将所有缓冲数据从管道移动到套接字。
// 
// 不变量：当进入SpicePump时，管道中正好有inPipe 
// 字节的数据，来自上一次对SpiceDrain的调用。
// 
// 与接头排水管的情况类似，如果接头返回
// 只需轮询插座是否准备就绪。
// EAGAIN，则接头泵
// 
// 如果拼接泵无法在对
// 拼接（2）的一次调用中移动所有数据，它将在缓冲数据上循环，直到将
// 所有数据写入套接字。此行为类似于用户空间中io.Copy的写入
// 步骤。
func splicePump(sock *FD, pipefd int, inPipe int) (int, error) {
	if err := sock.writeLock(); err != nil {
		return 0, err
	}
	defer sock.writeUnlock()
	if err := sock.pd.prepareWrite(sock.isFile); err != nil {
		return 0, err
	}
	written := 0
	for inPipe > 0 {
		n, err := splice(sock.Sysfd, pipefd, inPipe, spliceNonblock)
		// 在这里，决不能观察条件n==0&&err==nil，因为Splice控制管道的写入端。
		if n > 0 {
			inPipe -= n
			written += n
			continue
		}
		if err != syscall.EAGAIN {
			return written, err
		}
		if err := sock.pd.waitWrite(sock.isFile); err != nil {
			return written, err
		}
	}
	return written, nil
}

// 接头封装接头系统调用。由于当前的实现
// 仅在套接字和管道上使用拼接，因此未使用偏移参数。
// splice返回int而不是int64，因为呼叫者从不要求它在一次调用中移动超过int32的数据量。
func splice(out int, in int, max int, flags int) (int, error) {
	n, err := syscall.Splice(in, nil, out, nil, max, flags)
	return int(n), err
}

type splicePipe struct {
	rfd  int
	wfd  int
	data int
}

// Pipepool缓存管道，以避免高频构建和破坏管道缓冲区。
// 垃圾收集器将定期释放sync.Pool中的所有管道，因此我们需要为每个管道设置一个终结器，以便在实际GC之前关闭其文件描述符。
var splicePipePool = sync.Pool{New: newPoolPipe}

func newPoolPipe() interface{} {
	// 放弃创建管道缓冲区期间发生的错误，
	// 将数据传输重定向到传统方式，使用read（）+write（）作为回退。
	p := newPipe()
	if p == nil {
		return nil
	}
	runtime.SetFinalizer(p, destroyPipe)
	return p
}

// getPipe尝试从池中获取管道缓冲区，或者如果从缓存中获取nil，则使用newPipe（）创建一个新的管道缓冲区。
// 
// 请注意，它可能无法通过newPipe（）创建新的管道缓冲区，在这种情况下，getPipe（）将返回一个通用错误
// 并以字符串形式显示系统调用名称拼接。
func getPipe() (*splicePipe, string, error) {
	v := splicePipePool.Get()
	if v == nil {
		return nil, "splice", syscall.EINVAL
	}
	return v.(*splicePipe), "", nil
}

func putPipe(p *splicePipe) {
	// 如果管道中仍有数据，则
	// 然后关闭并丢弃它，而不是将其放回池中。
	if p.data != 0 {
		runtime.SetFinalizer(p, nil)
		destroyPipe(p)
		return
	}
	splicePipePool.Put(p)
}

var disableSplice unsafe.Pointer

// 新建管道为拼接操作设置管道。
func newPipe() (sp *splicePipe) {
	p := (*bool)(atomic.LoadPointer(&disableSplice))
	if p != nil && *p {
		return nil
	}

	var fds [2]int
	// pipe2是在2.6.27中添加的，我们的最低要求是2.6.23，因此可能无法实现。返回管道是可能的，但在
	// 2.6.29之前，当连接处于
	// 关闭状态时，接头返回-EAGAIN而不是0。
	const flags = syscall.O_CLOEXEC | syscall.O_NONBLOCK
	if err := syscall.Pipe2(fds[:], flags); err != nil {
		return nil
	}

	sp = &splicePipe{rfd: fds[0], wfd: fds[1]}

	if p == nil {
		p = new(bool)
		defer atomic.StorePointer(&disableSplice, unsafe.Pointer(p))

		// F_GETPIPE_SZ是在2.6.35中添加的，它没有-EAGAIN错误。
		if _, _, errno := syscall.Syscall(unix.FcntlSyscall, uintptr(fds[0]), syscall.F_GETPIPE_SZ, 0); errno != 0 {
			*p = true
			destroyPipe(sp)
			return nil
		}
	}

	return
}

// 破坏管道破坏管道。
func destroyPipe(p *splicePipe) {
	CloseFunc(p.rfd)
	CloseFunc(p.wfd)
}
